const socketProtocol = (location.protocol === 'https:' ? 'wss' : 'ws')
const socketHost = `${location.hostname}:__HMR_SERVER_PORT__`
const socket = new WebSocket(`${socketProtocol}://${socketHost}`, 'vite-hmr')

// Listen for messages
socket.addEventListener('message', async ({ data }) => {
  const payload = JSON.parse(data);
  if (payload.type === 'multi') {
    payload.updates.forEach(handleMessage)
  } else {
    handleMessage(payload)
  }
})

socket.addEventListener('close', () => {
  console.log(`[vite] server connection lost. polling for restart...`)
  setInterval(() => {
    fetch('/')
      .then(() => {
        location.reload()
      })
      .catch((e) => {
        console.error(e)
      })
  }, 1000)
})

async function handleMessage(payload) {
  const { path, changeSrcPath, timestamp } = payload;
  switch (payload.type) {
    case 'connected':
      console.log(`[vite] connected.`)
      break
    case 'vue-reload':
      queueUpdate(
        import(`${path}?t=${timestamp}`)
          .then((m) => () => {
            __VUE_HMR_RUNTIME__.reload(path, m.default)
            console.log(`[vite] ${path} reloaded.`)
          })
      )
      break
    case 'vue-rerender':
      const templatePath = `${path}?type=template`
      import(`${templatePath}&t=${timestamp}`).then((m) => {
        __VUE_HMR_RUNTIME__.rerender(path, m.render)
        console.log(`[vite] ${path} template updated.`)
      })
      break
    case 'style-update':
      const el = document.querySelector(`link[href*='${path}']`)
      if (el) {
        el.setAttribute(
          'href',
          `${path}${path.includes('?') ? '&' : '?'}t=${timestamp}`
        )
        break
      }
      // imported CSS
      const importQuery = path.includes('?') ? '&import' : '?import'
      await import(`${path}${importQuery}&t=${timestamp}`)
      console.log(`[vite] ${path} updated.`)
      break
    case 'style-remove':
      removeStyle(payload.id)
      break
    case 'js-update':
      queueUpdate(updateModule(path, changeSrcPath, timestamp))
      break
    case 'custom':
      console.log('custom');
      break
    case 'full-reload':
      if (path.endsWith('.html')) {
        const pagePath = location.pathname
        if (
          pagePath === path ||
          (pagePath.endsWith('/') && pagePath + 'index.html' === path)
        ) {
          location.reload()
        }
        return
      } else {
        location.reload()
      }
  }
}

let pending = false;
let queued = [];

async function queueUpdate(p) {
  queued.push(p)
  if (!pending) {
    pending = true
    await Promise.resolve()
    pending = false
    const loading = [...queued]
    queued = []
      ; (await Promise.all(loading)).forEach((fn) => fn && fn())
  }
}

const hotModulesMap = new Map();
const disposeMap = new Map();
const dataMap = new Map();
const customUpdateMap = new Map();

async function updateModule(
  id,
  changedPath,
  timestamp
) {
  const moduleMap = new Map()
  const isSelfUpdate = id === changedPath

  const modulesToUpdate = new Set()
  if (isSelfUpdate) {
    modulesToUpdate.add(id)
  }

  await Promise.all(
    Array.from(modulesToUpdate).map(async (dep) => {
      const disposer = disposeMap.get(dep)
      if (disposer) await disposer(dataMap.get(dep))
      const newMod = await import(
        dep + (dep.includes('?') ? '&' : '?') + `t=${timestamp}`
      )
      moduleMap.set(dep, newMod)
    })
  )

  return () => {

    console.log(`[vite]: js module hot updated: `, id)
  }
}

const supportsConstructedSheet = (() => {
  try {
    new CSSStyleSheet()
    return true
  } catch (e) { }
  return false
})()

const sheetsMap = new Map()

export function updateStyle(id, content) {
  let style = sheetsMap.get(id)
  if (supportsConstructedSheet && !content.includes('@import')) {
    if (style && !(style instanceof CSSStyleSheet)) {
      removeStyle(id)
      style = undefined
    }

    if (!style) {
      style = new CSSStyleSheet()
      style.replaceSync(content)
      document.adoptedStyleSheets = [...document.adoptedStyleSheets, style]
    } else {
      style.replaceSync(content)
    }
  } else {
    if (style && !(style instanceof HTMLStyleElement)) {
      removeStyle(id)
      style = undefined
    }

    if (!style) {
      style = document.createElement('style')
      style.setAttribute('type', 'text/css')
      style.innerHTML = content
      document.head.appendChild(style)
    } else {
      style.innerHTML = content
    }
  }
  sheetsMap.set(id, style)
}

function removeStyle(id) {
  let style = sheetsMap.get(id)
  if (style) {
    if (style instanceof CSSStyleSheet) {
      const index = document.adoptedStyleSheets.indexOf(style)
      document.adoptedStyleSheets = document.adoptedStyleSheets.filter(
        (s) => s !== style
      )
    } else {
      document.head.removeChild(style)
    }
    sheetsMap.delete(id)
  }
}